Vue Router 与前端路由
涵盖 Vue Router 两种模式、核心 API、路由守卫、懒加载、动态路由;以及前端路由底层原理、手写简易路由。原文件"vue-router"和"前端路由"两节合并到本文。
第一部分:前端路由基础
标准面试回答(1 分钟)
前端路由是指在前端维护 URL 和视图的映射关系,在不刷新页面的情况下,根据 URL 变化动态渲染对应组件的机制,核心是实现 SPA 的页面切换体验。
前端路由的底层原理基于两种实现方式:
第一种,Hash 模式:利用 URL 中 # 后面的部分(hash)不会触发页面刷新的特性,通过监听 hashchange 事件来感知 hash 变化,然后根据当前 hash 渲染对应组件。hash 值的变化不会发送到服务端,因此兼容性好,不需要服务端额外配置,但 URL 看起来不够美观。
第二种,History 模式:基于 HTML5 的 History API,核心是 pushState 和 replaceState 方法,可以改变浏览器的 URL 而不刷新页面,同时通过监听 popstate 事件来响应用户的前进后退操作。这种方式 URL 更干净,但需要服务端配合做兜底配置(将所有路由指向 index.html),否则刷新页面会 404。
核心实现要素:
- 路由映射表:存储路径和组件的对应关系
- 路由监听:hash 变化或 popstate 事件触发路由更新
- 路由匹配:根据当前 URL 找到对应的组件
- 视图渲染:将匹配到的组件渲染到指定容器中
前端路由的价值:实现页面切换无刷新,提升用户体验;可以维护浏览历史记录,支持前进后退;支持嵌套路由、路由参数、权限控制等复杂场景。
Vue Router、React Router 等框架路由库都是在这个原理基础上封装了更丰富的 API 和功能。
20 秒极简版
前端路由是 SPA 的核心机制,通过监听 URL 变化渲染对应组件,无需刷新页面。两种实现方式:Hash 模式靠 # 和 hashchange,兼容性好;History 模式靠 pushState 和 popstate,URL 更干净但需服务端配合。
追问 1:pushState、replaceState 与 popstate
pushState 和 replaceState 的区别:
pushState:在历史记录栈中添加一条新记录,浏览器的"后退"按钮可以回到上一个状态replaceState:替换当前历史记录,不会增加记录条数,替换后无法通过"后退"回到被替换前的状态
两者的共同点是都不会触发页面刷新,都只改变 URL。
popstate 触发时机:
- 用户点击浏览器的"前进""后退"按钮时触发
- 在 JS 中调用
history.back()、history.forward()、history.go()时触发 - 注意:直接调用
pushState或replaceState不会触发popstate事件
所以在前端路由的实现中,调用 pushState 改变 URL 后,需要手动执行路由匹配和视图更新;而 popstate 事件监听用于处理用户的前进后退操作。
追问 2:Hash 模式和 History 模式在 SEO 上的差异
Hash 模式对 SEO 不友好:搜索引擎爬虫在抓取页面时,# 后面的内容通常不会被发送到服务端,也不会被索引。对于需要 SEO 的网站(如内容展示类、官网),Hash 模式会导致页面无法被搜索引擎收录。
History 模式对 SEO 相对友好:URL 是真实路径,搜索引擎会将其视为独立的页面资源进行抓取。但需要服务端做 SSR(服务端渲染)或预渲染才能真正对 SEO 生效——如果只是前端 SPA + History 模式,页面内容仍然是 JS 动态渲染的,爬虫可能执行不了 JS,所以 SEO 效果有限。
结论:
- 对内后台管理系统、不需要 SEO 的页面 → Hash 模式足够,配置简单
- 对外官网、营销页、需要 SEO 的 SPA → 用 History 模式 + SSR(如 Nuxt.js)或预渲染方案
- 单纯 History 模式不做 SSR,SEO 提升有限
追问 3:手写简易前端路由
class SimpleRouter {
constructor(routes) {
this.routes = routes
this.currentPath = ''
// 监听 hash 变化
window.addEventListener('hashchange', this.onHashChange.bind(this))
// 初始化
this.onHashChange()
}
onHashChange() {
// 获取 hash,去掉开头的 #
let hash = window.location.hash.slice(1)
if (!hash) hash = '/' // 默认首页
this.currentPath = hash
// 匹配路由,找不到就 404
const route = this.routes[hash]
if (route) {
this.render(route.component)
} else {
this.render(this.routes['404']?.component || '<div>404</div>')
}
}
render(component) {
const app = document.getElementById('app')
if (typeof component === 'string') {
app.innerHTML = component
} else if (component.render) {
// 假设组件有 render 方法
app.innerHTML = component.render()
}
}
push(path) {
window.location.hash = path
}
}
// 使用
const router = new SimpleRouter({
'/': { component: '<div>首页</div>' },
'/about': { component: '<div>关于</div>' }
})History 模式版本需要监听 popstate,用 pushState 改 URL,并处理服务端 404 问题。
易错点
- 混淆"前端路由"和"后端路由":前端路由是客户端维护 URL 和视图映射,后端路由是服务端根据路径返回资源
- 只讲用法不讲原理:说不清
hashchange和pushState的区别 - History 模式刷新 404 的原因说不清
- popstate 触发时机答错:常见错误是"
pushState也会触发popstate" - 忽略 SEO 与 SSR 的关系:只说"History 模式对 SEO 友好",不提"需要 SSR"
- 手写路由时遗漏默认路由和 404
- 混淆
window.location的属性和方法
第二部分:Vue Router
标准面试回答(1 分钟)
Vue Router 是 Vue.js 官方的路由管理器,用于构建单页面应用(SPA),核心能力是在不刷新页面的情况下,根据 URL 的变化渲染对应的组件,同时提供路由守卫、导航控制、路由懒加载等高级功能。
把 Vue Router 的核心知识分成三个维度:
两种模式
- Hash 模式:默认模式,URL 中带
#,通过监听hashchange事件实现路由切换。兼容性好,但看起来不够美观,#后面的内容不会发送到服务端。 - History 模式:基于 HTML5 History API,使用
pushState和replaceState,URL 更干净。但需要服务端配合做兜底配置(比如 nginx 配置try_files),否则刷新会 404。
核心组成
- 路由配置:
routes数组,定义路径和组件的映射关系 - 路由视图:
<router-view>作为组件渲染的出口 - 路由链接:
<router-link>替代<a>标签,支持导航高亮 - 路由对象:
$route(当前路由信息)和$router(路由实例,用于编程式导航)
高级特性
- 路由守卫:全局守卫(
beforeEach、afterEach)、路由独享守卫、组件内守卫(beforeRouteEnter等),用于权限控制、登录拦截 - 路由懒加载:结合动态 import,实现按需加载,优化首屏性能
- 动态路由:
/user/:id这种参数化路径 - 嵌套路由:在组件内部嵌套
<router-view>,实现二级菜单等布局 - 命名视图:一个页面多个
<router-view>按 name 分别渲染
Vue Router 3.x 对应 Vue 2,Vue Router 4.x 对应 Vue 3。Vue Router 4 中 API 有变化,比如 new Router 改为 createRouter,mode 改为 history 配置,路由守卫的写法也略有调整。
20 秒极简版
Vue Router 是 Vue 的官方路由库,核心是监听 URL 变化渲染对应组件,实现 SPA。两种模式:Hash(带 #,兼容性好)和 History(需服务端配置)。关键概念:路由配置、<router-view>、<router-link>、编程式导航、路由守卫、懒加载。
追问 1:History 模式刷新为什么会 404?
原因:Hash 模式下,# 后面的内容不会发送到服务端,所有路由请求其实都是请求 index.html,由前端路由接管。而 History 模式下,URL 是真实的路径,比如 /user/123。刷新页面时,浏览器会向服务端请求这个路径,如果服务端没有对应的资源,就会返回 404。
解决方案:需要在服务端配置"所有路由都返回 index.html",让前端路由自己处理。
常见的配置:
Nginx:
location / {
try_files $uri $uri/ /index.html;
}Node.js (Express):
app.use(express.static('dist'))
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'dist', 'index.html'))
})另外要注意:History 模式下,静态资源(JS、CSS)的路径需要使用绝对路径或正确配置 publicPath,否则刷新后资源也会 404。
追问 2:路由守卫怎么实现登录拦截?
用全局前置守卫 beforeEach 实现登录拦截。完整流程:
router.beforeEach((to, from, next) => {
// 1. 判断是否需要登录
const requiresAuth = to.meta.requiresAuth
// 2. 获取登录状态(通常从 store 或 localStorage 取 token)
const isLoggedIn = store.state.user.isLoggedIn
// 3. 路由拦截逻辑
if (requiresAuth && !isLoggedIn) {
// 未登录,跳转到登录页,并携带目标路由以便登录后跳回
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else if (to.path === '/login' && isLoggedIn) {
// 已登录访问登录页,重定向到首页
next('/')
} else {
// 正常放行
next()
}
})关键细节:
- 必须调用
next()或next(path),否则路由会卡住 - 路由元信息
meta用于标记哪些路由需要登录 - 登录成功后,用
router.push(redirect)跳回目标页面 - 权限更复杂的场景,还可以在守卫中动态添加路由(
router.addRoute)
追问 3:动态路由怎么添加?路由懒加载怎么实现?
动态路由添加:常用于权限控制,根据用户角色动态生成路由。
Vue Router 3 中用 router.addRoutes,Vue Router 4 中用 router.addRoute。
// 根据权限动态添加
const adminRoutes = [
{ path: '/admin', component: () => import('@/views/Admin.vue') }
]
adminRoutes.forEach(route => router.addRoute(route))注意:动态添加的路由需要配合 router.push 或重新触发导航才能生效。一般会在登录后获取权限列表,动态添加路由后再跳转。
路由懒加载:使用动态 import,让路由组件在访问时才加载,而不是打包到主 bundle 里。
const routes = [
{
path: '/user',
component: () => import('@/views/User.vue')
}
]结合 webpack 的魔法注释可以自定义 chunk 名:
component: () => import(/* webpackChunkName: "user" */ '@/views/User.vue')在 Vue Router 4 中,还可以配合 Vue 3 的异步组件 defineAsyncComponent,但直接 () => import() 更简洁。
懒加载的优点是按需加载,减小首屏体积,缺点是切换时可能有加载延迟,通常配合骨架屏或 loading 优化体验。
易错点
- 模式原理讲不清楚:只说"Hash 模式带 #,History 不带",说不清底层依赖(
hashchangevspushState)和刷新 404 的原因 - History 模式服务端配置答不上来
- 路由守卫忘记
next:写守卫时忘记调用next或者逻辑分支漏掉next - 混淆
$route和$router:$route是路由信息对象,$router是路由实例 - 动态路由 API 版本混淆:Vue Router 3 的
addRoutes和 Vue Router 4 的addRoute用法不同 - 路由守卫中直接操作
location.href:会触发页面刷新,失去 SPA 意义 - 懒加载的 chunk 命名说不清楚
- 嵌套路由和命名视图混淆:嵌套路由是路由层级嵌套,命名视图是同一级渲染多个视图
Vue 3 中 keep-alive 与 Router 的结合
详见 06-Vue scoped、keep-alive、插槽、过滤器 的"keep-alive 与 Router"部分。